​
<template>
  <div>
    <el-form ref="formRef" :model="model" :rules="rules" :inline="inline" :label-width="labelWidth"
      :label-position="labelPosition">
      <slot name="head" />
      <template v-for="(item, index) in fields" :key="index" >
        <el-form-item  v-if="!item.show"  :prop="item.prop" :label="item.label"
        :label-width="item.labelWidth" :style="item.width">
        <!-- 插槽 -->
        <slot v-if="$slots[item.prop]" :name="item.prop"></slot>
        <span v-else style="width: 100%;">
          <!-- 输入框 -->
          <el-input @mouseleave="closeTips" @mouseenter="openTips(item, model[item.prop])" v-if="item.type === 'input'"
            clearable v-model="model[item.prop]" :placeholder="item.placeholder" :size="item.size"
            :disabled="item.disabled" :readonly="item.readonly" :icon="item.icon"
            :prefix-icon="item.prefix ? item.prefix : null" :suffix-icon="item.suffix ? item.suffix : null"
            :maxlength="item.maxlength" @input="input(item.prop)" :number="item.number" :rows="item.rows"
            :autosize="item.autosize" :style="item.elemWidth">
            <template #suffix>
              <span class="suffix">
                <span class="unit">{{ item.unit }}</span>
                <!-- 有icon -->
                <svg v-if="item.suffixIcon" :class="['icon']" aria-hidden="true">
                  <use :xlink:href="`#${item.suffixIcon}`" />
                </svg>
              </span>
            </template>
          </el-input>
          <!-- 数字输入框 -->
          <el-input @mouseleave="closeTips" @mouseenter="openTips(item, model[item.prop])" v-if="item.type === 'number'"
            :max="item.max" :min="item.min" v-model="model[item.prop]" type="number" :placeholder="item.placeholder"
            @keydown.native.up.prevent @keydown.native.down.prevent @input="input(item.prop)"
            @keydown.native.tab.prevent :size="item.size" :disabled="item.disabled" :style="item.elemWidth">
            <template #suffix>
              <span class="suffix">
                <span class="unit">{{ item.unit }}</span>
                <!-- 有icon -->
                <svg v-if="item.suffixIcon" :class="['icon']" aria-hidden="true">
                  <use :xlink:href="`#${item.suffixIcon}`" />
                </svg>
              </span>
            </template>
          </el-input>

          <!-- 密码输入框 -->
          <el-input v-if="item.type === 'password'" :max="item.max" :min="item.min" show-password
            v-model="model[item.prop]" :placeholder="item.placeholder" :size="item.size" :disabled="item.disabled"
            :style="item.elemWidth" />
          <!-- 文本输入框 -->
          <el-input @mouseleave="closeTips" @mouseenter="openTips(item, model[item.prop])"
            v-if="item.type === 'textarea'" :max="item.max" :min="item.min" type="textarea" v-model="model[item.prop]"
            :placeholder="item.placeholder" :size="item.size" :disabled="item.disabled" :style="item.elemWidth"
            :rows="item.row ? item.row : 5" />
          <!-- 下拉框选择器 -->
          <el-select v-if="item.type === 'select'" collapse-tags clearable v-model="model[item.prop]"
            @change="change(item.prop)" no-match-text="无匹配数据" no-data-text='无匹配数据' :placeholder="item.placeholder"
            :size="item.size" :disabled="item.disabled" :filterable="item.filterable" :multiple="item.multiple"
            :style="item.elemWidth">
            <el-option v-for="opt in options[item.prop]" :key="opt[item.bindValue] ? opt[item.bindValue] : opt.value"
              :label="opt[item.bindLabel] ? opt[item.bindLabel] : opt.name"
              :value="opt[item.bindValue] ? opt[item.bindValue] : opt.value" :disabled="opt.disabled" />
          </el-select>
          <!-- 下拉框选择器可以新增创建 -->
          <el-select v-if="item.type === 'select_create'" clearable v-model="model[item.prop]"
            @change="change(item.prop)" no-match-text="无匹配数据" no-data-text='无匹配数据' allow-create
            :placeholder="item.placeholder" :size="item.size" :disabled="item.disabled" :filterable="item.filterable"
            :multiple="item.multiple" :style="item.elemWidth">
            <el-option v-for="opt in options[item.prop]" :key="opt[item.bindValue] ? opt[item.bindValue] : opt.value"
              :label="opt[item.bindLabel] ? opt[item.bindLabel] : opt.label"
              :value="opt[item.bindValue] ? opt[item.bindValue] : opt.value" :disabled="opt.disabled" />
          </el-select>
          <el-date-picker v-model="model[item.prop]" v-if="item.type === 'dateTime'" type="date"
            :placeholder="item.placeholder" />
          <!-- 日期选择器 -->
          <el-date-picker v-if="item.type === 'date'" clearable :type="item.type" v-model="model[item.prop]"
            :options="item.options" :placeholder="item.placeholder" :disabled-date="disabledDates[item.prop]"
            :size="item.size" :disabled="item.disabled" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
            @change="change(item.prop)" />


          <el-date-picker v-if="item.type === 'year'" clearable :type="item.type" v-model="model[item.prop]"
            :options="item.options" value-format="YYYY" format="YYYY" :placeholder="item.placeholder" :size="item.size"
            :disabled="item.disabled" />

          <el-date-picker v-model="model[item.prop]" v-if="item.type === 'range'" type="daterange" range-separator="至"
            start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD" format="YYYY-MM-DD" />
          <!-- 时间选择器 -->
          <el-time-picker v-if="item.type === 'time'" clearable :type="item.type" v-model="model[item.prop]"
            :format="item.format" :value-format="item.valueFormat" :placeholder="item.placeholder"
            :disabled-hours="item.disabledHours ? item.disabledHours : null"
            :disabled-minutes="item.disabledMinutes ? item.disabledMinutes : null"
            :disabled-seconds="item.disabledSeconds ? item.disabledSeconds : null" :size="item.size"
            :disabled="item.disabled" :style="item.elemWidth" />

          <el-date-picker v-model="model[item.prop]" v-if="item.type === 'datetime'" clearable type="datetime"
            value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" :placeholder="item.placeholder" />
          <!-- 单选框 -->
          <el-radio-group @change="change(item.prop)" v-if="item.type === 'radio'" v-model="model[item.prop]"
            :size="item.size">
            <el-radio v-for="opt in options[item.prop]" :value="opt.value || opt.id" :size="item.size" :fill="item.fill"
              :disabled="item.disabled">{{ opt.label || opt.name }}</el-radio>
          </el-radio-group>
          <el-switch v-model="model[item.prop]" v-if="item.type === 'switch'" class="ml-2" :active-value="item.active"
            :inactive-value="item.inactive" :active-text="item.activeText" :inactive-text="item.inactiveText"
            style="--el-switch-on-color:#13ce66; " />
        </span>
      </el-form-item>
      </template>
     

    </el-form>
    <el-tooltip v-model:visible="visible" :content="content" placement="bottom" trigger="click" virtual-triggering
      :virtual-ref="triggerRef" />
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, reactive, onMounted, onUnmounted, toRefs, unref } from "vue";
import { ElMessage } from 'element-plus'
import { Message } from "../../utils/resetMessage";
type item = {
  type?: string;
  show?: boolean;
  label: string;
  placeholder: string;
  prop: string,
  bindLabel: string,
  bindValue: string
  valueFormat: any
  max: number,
  elemWidth: any,
  number: number,
  format: any,
  disabledHours: string,
  disabled: boolean,
  fill: any,
  size: number,
  disabledMinutes: any,
  disabledSeconds: any,
  prefix: any,
  rows: any,
  autosize: number,
  filterable: boolean,
  labelWidth: string,
  width: string,
  multiple: boolean,
  readonly: any,
  icon: string,
  suffix: any,
  options: any,
  min: number,
  row?: number,
  active?: number | string | boolean,
  inactive?: number | string | boolean,
  activeText?: string | null,
  inactiveText?: string | null,
  unit?: string | null,
  suffixIcon?: string | null,
  maxlength?: string | number
};

export default defineComponent({
  components: {
  },
  props: {
    fields: { type: Array as () => item[], default: [] },
    options: { type: Object as () => any, default: {} },
    disabledDates: { type: Object as () => any, default: {} },
    model: { type: Object as () => any, default: {} }, // 表单数据对象
    rules: Object, // 表单验证对象
    width: Number, // 表单宽度
    labelWidth: { Number, default: "120px" }, // 表单域标签宽度
    labelPosition: {
      type: String,
      default: "right",
    }, // 表单域标签位置
    inline: {
      type: Boolean,
      default: true,
    }, // 行内表单模式
    loading: {
      type: Boolean,
      default: false,
    }, // 表单加载状态
    btnLabelWidth: Number, // 表单域标签的宽度(按钮区)
    submit: {
      type: Boolean,
      default: false,
    }, // 是否显示提交按钮
    reset: {
      type: Boolean,
      default: false,
    }, // 是否显示重置按钮
    button: {
      type: Boolean,
      default: true,
    }, // 是否显示普通按钮
    submitText: {
      type: String,
      default: "Submit",
    }, // 提交按钮文字
    resetText: {
      type: String,
      default: "Reset",
    }, // 重置按钮文字
    buttonText: {
      type: String,
      default: "Button",
    }, // 普通按钮文字
    okType: {
      type: String,
      default: "primary",
    }, // 提交按钮类型
    btnType: String, // 普通按钮类型
    btnLong: Boolean, // 设置按钮的长度为 100%
    // 设置按钮的图标类型
    okIcon: String,
    btnIcon: String,
    btnLoading: {
      type: Boolean,
      default: false,
    }, // 按钮加载状态
    btnType2: {
      type: String,
      default: "primary",
    }, // 普通按钮类型
    btnLong2: Boolean, // 设置按钮的长度为 100%
    // 设置按钮的图标类型
    btnIcon2: String,
  },
  name: "ikechen-form",
  setup(props: any, { emit }: any) {
    const state = reactive({
      visible: false,
      content: "",
    });
    const formRef = ref()
    const methods = {
      change(prop: string) {
        emit("handleSelect", prop);
      },
      input(prop: string) {
        emit("handleInput", prop);
      },
      verification() {
        const formvalid = unref(formRef)
        return new Promise((resolve, reject) => {
          formvalid.validate((valid: boolean) => {
            if (valid) {
              resolve(true)
            } else {
              Message({ type: 'error', message: '信息未填写完整' })
              reject(false)
            }
          })
        })
      },
      resetFields() {
        const formvalid = unref(formRef)
        formvalid.resetFields();
      },
      clearValidate() {
        const formvalid = unref(formRef)
        formvalid.clearValidate();
      },
      openTips(item: any, value: any) {
        if (!item.disabled || !value) return
        state.content = value
        state.visible = true
        // console.log(item,value);
      },
      closeTips() {
        state.content = ''
        state.visible = false
      }
    };
    const triggerRef = ref({
      getBoundingClientRect() {
        return position.value
      },
    })

    const position = ref({
      top: 0,
      left: 0,
      bottom: 0,
      right: 0,
    })

    const mousemoveHandler = (e) => {
      position.value = DOMRect.fromRect({
        width: 0,
        height: 0,
        x: e.clientX,
        y: e.clientY,
      })
    }
    onMounted(() => {
      document.addEventListener('mousemove', mousemoveHandler)
    })

    onUnmounted(() => {
      document.removeEventListener('mousemove', mousemoveHandler)
    })
    return {
      ...toRefs(state),
      ...methods, formRef,
      emit, triggerRef
    };
  },
});
</script>

<style></style>

​